package com.bckj.fastboot.core.extra.extpt;

import com.bckj.fastboot.core.lang.exception.BusinessException;
import com.bckj.fastboot.core.extra.stage.BaseStageContext;
import com.bckj.fastboot.core.extra.stage.Stage;
import com.bckj.fastboot.core.extra.stage.StageChain;
import com.bckj.fastboot.core.extra.stage.StageRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

@SuppressWarnings("unchecked")
@Slf4j
public class DynamicExtPtHandler implements InvocationHandler {

    private final StageRepository stageRepository;
    private final Map<String, ExtPtProfile.Method> methodMap;
    private final ConcurrentMap<String, StageChain<? extends BaseStageContext, ? extends Stage<? extends BaseStageContext>>>
            stageChainMap = new ConcurrentHashMap<>();

    public DynamicExtPtHandler(StageRepository stageRepository, ExtPtProfile profile) {
        this.stageRepository = stageRepository;
        methodMap = profile.getMethods().stream().peek(this::check).collect(Collectors.toMap(ExtPtProfile.Method::getMethodName, dm -> dm));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodMap.containsKey(methodName)) {
            ExtPtProfile.Method currMethod = methodMap.get(methodName);
            BaseStageContext arg = (BaseStageContext) args[0];
            return invoke(currMethod, arg);
        }
        return null;
    }

    protected void check(ExtPtProfile.Method em) {
        if (em.getType() != ExtPtProfile.TYPE_CHAIN) {
            throw new BusinessException("invalid method type");
        }
        Assert.notEmpty(em.getStages(), "stages can not be empty");
    }

    protected Object invoke(ExtPtProfile.Method em, BaseStageContext arg) throws Throwable {
        return invokeChain(em, arg);
    }

    protected Object invokeChain(ExtPtProfile.Method em, BaseStageContext arg) throws Throwable {
        String methodName = em.getMethodName();
        StageChain<BaseStageContext,Stage<BaseStageContext>> chain = (StageChain<BaseStageContext, Stage<BaseStageContext>>) stageChainMap.computeIfAbsent(methodName, key -> createChain(em));
        chain.execute(arg);
        return null;
    }

    private StageChain<? extends BaseStageContext, ? extends Stage<BaseStageContext>> createChain(ExtPtProfile.Method dm) {
        var chain = StageChain.newChain();
        for (String stageId : dm.getStages()) {
            Stage<BaseStageContext> stage = stageRepository.get(stageId);
            if (stage == null) {
                log.warn("stage not found: {}", stageId);
                continue;
            }
            chain.append(stage);
        }
        return chain;
    }

}
